﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace BayesDraw
{
    class Arrow
    {
        public static List<Arrow> AllArrows = new List<Arrow>(); 

        public RNode StartNode;
        public RNode EndNode;

        private string _tagStr = "";
        private GraphicsPath _arrowPath = new GraphicsPath();
        private GraphicsPath _tagPath = new GraphicsPath();
        private Region _preArrowRegion;
        private Region _arrowRegion = new Region();
        private Region _preTagRegion;
        private Region _tagRegion = new Region();
        private Control _parentControl;
        private Brush _backBrush;
        private bool _isDispose = false;


        FontFamily _tagFontFamily ;
        int _tagFontStyle ;
        int _tagEmSize ;
        StringFormat _strFormat ;

        public Arrow(RNode startNode, RNode endNode,Control parent)
        {
            if(startNode.Equals(endNode))
            {ThrowNodeSameException();return;}
            CheckRepetitionArrow(startNode, endNode);
            //CheckLoop();

            this.StartNode = startNode;
            this.StartNode.MoveEvent += Answer_MoveEvent;
            this.EndNode = endNode;
            this.EndNode.MoveEvent += Answer_MoveEvent;

            CreateNewArrow(startNode,endNode);

            this._parentControl = parent;
            this._backBrush = new SolidBrush(_parentControl.BackColor);

            AllArrows.Add(this);
            Init();
        }

        private void Init()
        {
            _tagFontFamily = new FontFamily("Arial");
            _tagFontStyle = (int)FontStyle.Regular;
            _tagEmSize = 20;
            _strFormat = StringFormat.GenericDefault;
        }

        /// <summary>
        /// 权值
        /// </summary>
        public int Weight { get; set; }

        public string TagStr
        {
            get { return _tagStr; }
            set
            {
                _tagStr = value;
                DrawArrow();
            }
        }

        void Answer_MoveEvent()
        {
            DrawArrow();
        }


        public Region ArrowRegion
        {
            get
            {
                _arrowRegion.MakeEmpty();
                _arrowRegion.Union(
                    ArrowPath(
                    new Point(
                        StartNode.GetMidPoint().X,
                        StartNode.GetMidPoint().Y),
                    new Point(
                        EndNode.GetMidPoint().X,
                        EndNode.GetMidPoint().Y))
                        );
                return _arrowRegion;
            }
        }

        public Region TagRegion
        {
            get
            {
                _tagRegion.MakeEmpty();
                _tagRegion.Union(TagPath);
                return _tagRegion;
            }
        }


        private GraphicsPath TagPath
        {
            get
            {
                GraphicsPath gp = new GraphicsPath();
                Point tagOrigin = new Point(GetMidPoint().X, GetMidPoint().Y - 10);
                gp.AddString(TagStr, _tagFontFamily, _tagFontStyle, _tagEmSize,
                    tagOrigin, _strFormat);
                return gp;
            }
        }

        private GraphicsPath ArrowPath(Point start, Point end)
        {
            int w = 2;//箭头宽度
            int w2 = 5;//箭头最宽处宽度
            double l = GetDistance(start, end);//长度
            double angle = PointToAngle(start, end, 1);//角度

            Point pL1 = new Point();
            Point pL2 = new Point();
            Point pM1 = new Point();
            Point pM2 = new Point();
            Point pR1 = new Point();
            Point pR2 = new Point();
            Point pM = new Point();

            start.X = (int)(start.X + 20 * Math.Cos(angle));
            start.Y = (int)(start.Y + 20 * Math.Sin(angle));

            end.X = (int)(start.X + (l - 45) * Math.Cos(angle));
            end.Y = (int)(start.Y + (l - 45) * Math.Sin(angle));

            pL1.X = (int)(start.X - w * Math.Sin(angle));
            pL1.Y = (int)(start.Y + w * Math.Cos(angle));

            pL2.X = (int)(start.X + w * Math.Sin(angle));
            pL2.Y = (int)(start.Y - w * Math.Cos(angle));

            pM.X = (int)(start.X + (l - 60) * Math.Cos(angle));
            pM.Y = (int)(start.Y + (l - 60) * Math.Sin(angle));

            pM1.X = (int)(pM.X - w * Math.Sin(angle));
            pM1.Y = (int)(pM.Y + w * Math.Cos(angle));

            pM2.X = (int)(pM.X + w * Math.Sin(angle));
            pM2.Y = (int)(pM.Y - w * Math.Cos(angle));

            pR1.X = (int)(pM.X - w2 * Math.Sin(angle));
            pR1.Y = (int)(pM.Y + w2 * Math.Cos(angle));

            pR2.X = (int)(pM.X + w2 * Math.Sin(angle));
            pR2.Y = (int)(pM.Y - w2 * Math.Cos(angle));

            GraphicsPath gp = new GraphicsPath();
            gp.AddPolygon(new Point[] { pL1, pM1, pR1, end, pR2, pM2, pL2 });
            return gp;
        }

        /// <summary>
        /// 绘制箭头
        /// </summary>
        public void DrawArrow()
        {
            if (!_isDispose)
            {
                Graphics g = _parentControl.CreateGraphics();

                //g.Clear(_parentControl.BackColor);
                if (_preArrowRegion != null)
                    g.FillRegion(_backBrush, this._preArrowRegion);
                if (_preTagRegion != null)
                    g.FillRegion(_backBrush, this._preTagRegion);

                g.FillRegion(Brushes.Turquoise, this.ArrowRegion);
                g.FillRegion(Brushes.Yellow, this.TagRegion);

                _preArrowRegion = _arrowRegion;
                _preTagRegion = _tagRegion;
            }

        }

        /// <summary>
        /// 获取箭头的中间点
        /// </summary>
        /// <returns></returns>
        public Point GetMidPoint()
        {
            //中间点
            Point p1 = new Point(
                StartNode.Location.X + StartNode.Width / 2,
                StartNode.Location.Y + StartNode.Height / 2);
            Point p2 = new Point(
                EndNode.Location.X + EndNode.Width / 2,
                EndNode.Location.Y + EndNode.Height / 2);

            Point p = new Point();
            p.X = (p1.X + p2.X) / 2;
            p.Y = (p1.Y + p2.Y) / 2;
            return p;
        }


        /// <summary>
        /// 更改起始节点
        /// </summary>
        /// <param name="newStartNode"></param>
        public void ChangeStartNode(RNode newStartNode)
        {
            if (EndNode.Equals(newStartNode))
            {ThrowNodeSameException();return;}
            CheckRepetitionArrow(newStartNode, EndNode);

            RNode preStartNode = StartNode;
            DisposeOldArrow(StartNode,EndNode);
            this.StartNode = newStartNode;
            CreateNewArrow(StartNode,EndNode);

            try
            {
                CheckLoop();
            }
            catch (Exception ex)
            {
                DisposeOldArrow(StartNode, EndNode);
                this.StartNode = preStartNode;
                CreateNewArrow(StartNode, EndNode);
                throw;
            }
            finally
            {
                this.DrawArrow();
            }
            

            
        }

        /// <summary>
        /// 更改末端节点
        /// </summary>
        /// <param name="newEndNode"></param>
        public void ChangeEndNode(RNode newEndNode)
        {
            if(StartNode.Equals(newEndNode))
            { ThrowNodeSameException(); return; }
            CheckRepetitionArrow(StartNode, newEndNode);

            RNode preEndNode = EndNode;
            DisposeOldArrow(StartNode,EndNode);
            this.EndNode = newEndNode;
            CreateNewArrow(StartNode,EndNode);

            try
            {
                CheckLoop();
            }
            catch (Exception ex)
            {
                DisposeOldArrow(StartNode, EndNode);
                this.EndNode = preEndNode;
                CreateNewArrow(StartNode, EndNode);
                throw;
            }
            finally
            {
                this.DrawArrow();
            }
            

            
        }

        private void CreateNewArrow(RNode startNode, RNode endNode)
        {
            StartNode.AddTargetNode(endNode, this);
            EndNode.AddOriginNode(startNode, this);
        }

        private void DisposeOldArrow(RNode startNode, RNode endNode)
        {
            StartNode.RemoveTargetNode(endNode, this);
            EndNode.RemoveOriginNode(startNode, this);
        }

        private void ThrowNodeSameException()
        {
            throw new ArgumentException("始节点不能与末节点相同");
        }

        private static void ThrowArrowSameException()
        {
            throw new ArgumentException("已经有这个始节点到末节点的边存在");
        }

        private static void ThrowArrowLoopException()
        {
            throw new ArgumentException("更改出错，图中将存在环路，已撤销更改。");
        }

        public void Dispose()
        {
            if (_isDispose) return;
            StartNode.TargetNodes.Remove(EndNode);
            EndNode.OriginNodes.Remove(StartNode);

            Graphics g = _parentControl.CreateGraphics();

            //g.Clear(_parentControl.BackColor);
            if (_preArrowRegion != null)
                g.FillRegion(_backBrush, this._preArrowRegion);
            if (_preTagRegion != null)
                g.FillRegion(_backBrush, this._preTagRegion);

            g.FillRegion(_backBrush, this.ArrowRegion);
            g.FillRegion(_backBrush, this.TagRegion);

            _preArrowRegion = _arrowRegion;
            _preTagRegion = _tagRegion;

            StartNode = null;
            EndNode = null;

            AllArrows.Remove(this);
            _isDispose = true;
        }

        public static void CheckRepetitionArrow(RNode startNode,RNode endNode)
        {
            List<Arrow> tmpArrows1 = new List<Arrow>();
            List<Arrow> tmpArrows2 = new List<Arrow>();
            int repeatCount = 0;
            foreach (Arrow arrow in AllArrows)
            {
                if(arrow.StartNode.Equals(startNode))
                    tmpArrows1.Add(arrow);
                if (arrow.StartNode.Equals(endNode))
                    tmpArrows2.Add(arrow);
            }
            foreach (Arrow arrow in tmpArrows1)
            {
                if (arrow.EndNode.Equals(endNode))
                    repeatCount++;
            }
            foreach (Arrow arrow in tmpArrows2)
            {
                if (arrow.EndNode.Equals(startNode))
                    repeatCount++;
            }
            if (repeatCount>0)
            {
                ThrowArrowSameException();
            }
        }

        public void CheckLoop()
        {
            foreach (RNode node in RNode.AllNodes)
            {
                ListTargetNodes(node.TargetNodes,node);
            }
        }

        private static void ListTargetNodes(List<RNode> nodes,RNode beginNode)
        {
            foreach (RNode node in nodes)
            {
                if (node.Equals(beginNode))
                {
                    ThrowArrowLoopException();
                    break;
                }
                ListTargetNodes(node.TargetNodes, beginNode);
            }
            
        }

        /// <summary>
        /// 计算两点间的角度
        /// </summary>
        /// <param name="AOrigin">原点坐标</param>
        /// <param name="APoint">参考点坐标</param>
        /// <param name="APoint">离心率</param>
        /// <returns>返回两点间的角度</returns>
        public static double PointToAngle(Point AOrigin, Point APoint, double AEccentricity)
        {
            double Result;
            if (APoint.X == AOrigin.X)
            {
                if (APoint.Y > AOrigin.Y)
                    Result = Math.PI * 0.5;
                else Result = Math.PI * 1.5;
            }
            else if (APoint.Y == AOrigin.Y)
            {
                if (APoint.X > AOrigin.X)
                    Result = 0;
                else Result = Math.PI;
            }
            else
            {
                Result = Math.Atan(
                    (double)(AOrigin.Y - APoint.Y) /
                    (double)(AOrigin.X - APoint.X) * AEccentricity);

                if ((APoint.X < AOrigin.X) && (APoint.Y > AOrigin.Y))
                    Result += Math.PI;
                else if ((APoint.X < AOrigin.X) && (APoint.Y < AOrigin.Y))
                    Result += Math.PI;
                else if ((APoint.X > AOrigin.X) && (APoint.Y < AOrigin.Y))
                    Result += 2 * Math.PI;
                else Result = Result;
            }
            return Result;

        }

        /// <summary>
        /// 计算两点间的距离
        /// </summary>
        /// <param name="p1">Point</param>
        /// <param name="p2">Point</param>
        /// <returns>距离</returns>
        public static double GetDistance(Point p1, Point p2)
        {
            return Math.Sqrt(Math.Abs(p1.X - p2.X) * Math.Abs(p1.X - p2.X)
                + Math.Abs(p1.Y - p2.Y) * Math.Abs(p1.Y - p2.Y));
        }


    }
}
